<!DOCTYPE HTML>
<link rel="help" href="https://w3c.github.io/pointerevents/#firing-events-using-the-pointerevent-interface">
<title>Enter/leave events fired to parent after child is removed from slot</title>
<meta name="variant" content="?mouse">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pointerevent_support.js"></script>

<template id="template">
  <style>
    div {
      width: 100px;
      height: 100px;
    }
  </style>
  <div id="parent">
    <slot id="slot">slot</slot>
  </div>
</template>

<style>
  div, my-elem {
    width: 100px;
    height: 100px;
    display: block;
  }
</style>

<my-elem id="host">
  <div id="child">child</div>
</my-elem>
<div id="done">done</div>

<script>
  "use strict";

  customElements.define(
    "my-elem",
    class extends HTMLElement {
      constructor() {
        super();
        let content = document.getElementById("template").content;
        const shadowRoot = this.attachShadow({ mode: "open" });
        shadowRoot.appendChild(content.cloneNode(true));
      }
    },
  );

  const pointer_type = location.search.substring(1);

  const shadow_host = document.getElementById("host");
  const parent = shadow_host.shadowRoot.getElementById("parent");
  const slot = parent.firstElementChild;
  const slotted_child = document.getElementById("child");
  const done = document.getElementById("done");

  let event_log = [];
  let elem_to_remove;

  function logEvent(e) {
    if (e.eventPhase == e.AT_TARGET) {
      event_log.push(e.type + "@" + e.target.id);
    }
  }

  function removeChildFromSlot() {
    elem_to_remove.remove();
    event_log.push("(child-removed)");
  }

  function restoreChildInSlot() {
    if (!slotted_child.parentElement) {
      shadow_host.appendChild(slotted_child);
    }
    if (!slot.parentElement) {
      parent.appendChild(slot);
    }
  }

  function setup() {
    const events = ["pointerover", "pointerout",
          "pointerenter", "pointerleave", "pointerdown", "pointerup"];
    let targets = [shadow_host, parent, slot, slotted_child];
    for (let i = 0; i < targets.length; i++) {
      events.forEach(event => targets[i].addEventListener(event, logEvent));
    }
  }

  function addPromiseTest(remover_event, tested_elem_to_remove,
        expected_events) {
    assert_true(["slot", "slotted-child"].includes(tested_elem_to_remove),
          "Unexpcted tested_elem_to_remove param");

    const test_name = `Pointer events from ${pointer_type} `+
          `received before/after ${tested_elem_to_remove} removal `+
          `at ${remover_event}`;

    promise_test(async test => {
      event_log = [];
      elem_to_remove = (tested_elem_to_remove == "slot" ? slot : slotted_child);

      restoreChildInSlot();
      child.addEventListener(remover_event, removeChildFromSlot,
            { once: true });
      // TODO(mustaq@chromium.org): It would be more robust if we could remove
      // the event listener above through `test.add_cleanup()` but strangely the
      // cleanup call fails after the test that removes the slotted-child!  This
      // happens even if we make the shadow DOM construction dynamic inside this
      // `promise_test`!!!

      let done_click_promise = getEvent("click", done);

      let actions = new test_driver.Actions()
          .addPointer("TestPointer", pointer_type)
          .pointerMove(-30, -30, {origin: shadow_host})
          .pointerDown()
          .pointerUp()
          .pointerMove(30, 30, {origin: shadow_host})
          .pointerDown()
          .pointerUp()
          .pointerMove(0, 0, {origin: done})
          .pointerDown()
          .pointerUp();

      await actions.send();
      await done_click_promise;

      let removal_in_event_log = event_log.indexOf("(child-removed)");
      let removal_in_expected_list = expected_events.indexOf("(child-removed)");
      assert_true(removal_in_event_log != -1 && removal_in_expected_list != -1,
          "(child-removed) expected in both lists");

      assert_equals(event_log.slice(0, removal_in_event_log).toString(),
          expected_events.slice(0, removal_in_expected_list).toString(),
          "events received before removal");
      assert_equals(event_log.slice(removal_in_event_log+1).toString(),
          expected_events.slice(removal_in_expected_list+1).toString(),
          "events received after removal");
    }, test_name);
  }

  setup();

  addPromiseTest(
    "pointerdown",
    "slot",
    [
      "pointerover@child",
      "pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
      "pointerdown@child", "(child-removed)",
      "pointerover@parent", "pointerover@host", "pointerup@parent", "pointerup@host",
      "pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
      "pointerout@parent", "pointerout@host",
      "pointerleave@parent", "pointerleave@host"
    ]
  );
  addPromiseTest(
    "pointerdown",
    "slotted-child",
    [
      "pointerover@child",
      "pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
      "pointerdown@child", "(child-removed)",
      "pointerleave@slot",
      "pointerover@parent", "pointerover@host", "pointerup@parent", "pointerup@host",
      "pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
      "pointerout@parent", "pointerout@host",
      "pointerleave@parent", "pointerleave@host"
    ]
  );
  addPromiseTest(
    "pointerup",
    "slot",
    [
      "pointerover@child",
      "pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
      "pointerdown@child", "pointerup@child", "(child-removed)",
      "pointerover@parent", "pointerover@host",
      "pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
      "pointerout@parent", "pointerout@host",
      "pointerleave@parent", "pointerleave@host"
    ]
  );
  addPromiseTest(
    "pointerup",
    "slotted-child",
    [
      "pointerover@child",
      "pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
      "pointerdown@child", "pointerup@child", "(child-removed)",
      "pointerleave@slot",
      "pointerover@parent", "pointerover@host",
      "pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
      "pointerout@parent", "pointerout@host",
      "pointerleave@parent", "pointerleave@host"
    ]
  );
</script>
